class ResourceHandler {
    config = {
        retryTimes: 1, // 重试上限
        replaceMap: {},
        allowGlobalReplace: true,
        globalReplaceTimes: 3, // 触发全域名替换的次数 以同一个域名下不同路径作为计数
    }
    // 资源重试次数映射 结构设计为 第一层key为 _origin 第二层key为pathname
    #resourceMap = Object.create(null)
    // 触发域名全替换
    #allReplaceList = new Set()
    // 定时器
    #timer = null
    // 是否页面加载完成
    isLoaded = false
    constructor(config = {}) {
        this.config = { ...this.config, ...config }
        this.#init()
    }
    // 初始化增加监听器
    #init() {
        // 页面加载完成后 不再使用write
        window.addEventListener('DOMContentLoaded', () => {
            this.isLoaded = true
        })
        window.addEventListener('error', (event) => {
            // 常规错误不做处理
            if (event instanceof ErrorEvent || !event.target) return
            const target = event.target
            if (!(target instanceof HTMLScriptElement || target instanceof HTMLLinkElement)) return
            // 静态资源错误
            const _isScript = target.tagName === 'SCRIPT'
            const _isStyle = target.tagName === 'LINK'
            this.#initTarget(target)
            const { _pathname, _origin } = target
            if (!this.#resourceMap[_origin]) {
                this.#resourceMap[_origin] = Object.create(null)
            }
            const currentRetryTimes = this.#resourceMap[_origin][_pathname] || 0
            const replaceDomain = this.config.replaceMap[_origin] || ''
            const hasReplaceDomains = replaceDomain.length > 0

            // 是否触发全局替换机制
            const hasTriggerGlobalReplace = this.#allReplaceList.has(_origin)
            if (this.config.allowGlobalReplace && hasTriggerGlobalReplace) {
                return this.#handleGlobalReplace(target, replaceDomain)
            }
            // 触发替换机制
            if (this.config.retryTimes <= currentRetryTimes) {
                if (hasReplaceDomains) {
                    this.#resourceMap[_origin][_pathname] = currentRetryTimes + 1
                    this.#handleReplace(target, replaceDomain)
                }
            } else {
                // 触发全局替换
                if (this.config.allowGlobalReplace && Object.keys(this.#resourceMap[_origin]).length >= this.config.globalReplaceTimes && hasReplaceDomains) {
                    this.#allReplaceList.add(_origin)
                    return this.#handleGlobalReplace(target, replaceDomain)
                }
                // 触发重试
                this.#resourceMap[_origin][_pathname] = currentRetryTimes + 1
                this.#handleRetry(target)
            }

        }, true)// 由于错误并不会冒泡,因此我们通过事件捕获阶段
    }
    // 初始化新增特定属性
    #initTarget(target) {
        if (target instanceof HTMLElement) {
            const _isScript = target.tagName === 'SCRIPT'
            const _isStyle = target.tagName === 'LINK'
            let resourcePath = ''
            if (_isScript) {
                target._isScript = _isScript // 标记
                target._isAsync = this.#getScriptExecutionMode(target)._isAsync // 判断是否为异步
                resourcePath = target.src
            }
            if (_isStyle) {
                target._isStyle = _isStyle // 标记
                resourcePath = target.href
            }
            target.allAttributesStr = this.#handleGetTagAttr(target)
            const url = new URL(resourcePath)
            target._pathname = target._pathname || url.pathname
            target._origin = target._origin || url.origin
            // 资源备用域名，存在即支持替换
            target._spareDomain = this.config.replaceMap[target._origin] || ''
        }
    }
    // 重试机制
    #handleRetry(target) {
        try {
            const { allAttributesStr } = target
            if (target._isScript) {
                // 异步标签
                if (target._isAsync || this.isLoaded) {
                    this.#handleInsertBefore(target, 'script')
                } else {
                    const script = `\<script ${allAttributesStr}><\/script>`
                    document.write(script)
                }
            }
            if (target._isStyle) {
                this.#handleInsertBefore(target, 'link')
            }
            target.remove()
        } catch (error) {
            console.log(error)
        }

    }
    // 替换机制
    async #handleReplace(target, replaceDomain) {
        try {
            const { _origin, _pathname } = target
            const replaceUrl = this.#handleGetReplaceUrl(_origin, _pathname, replaceDomain)
            if (!replaceUrl) return
            // script
            if (target._isScript) {
                target.setAttribute('src', replaceUrl)
                if (target._isAsync || this.isLoaded) {
                    this.#handleInsertBefore(target, 'script')
                } else {
                    let allAttributesStr = this.#handleGetTagAttr(target)
                    const script = `\<script ${allAttributesStr}><\/script>`
                    document.write(script)
                }
            }
            // style
            if (target._isStyle) {
                target.setAttribute('href', replaceUrl)
                this.#handleInsertBefore(target, 'link')
            }
            target.remove()
        } catch (error) {
            console.log(error)
        }

    }
    // 获取所有标签
    #handleGetTagAttr(tag) {
        const attributes = tag.attributes
        let allAttributesStr = ''
        Array.from(attributes).forEach(attr => {
            allAttributesStr += `${attr.name}="${attr.value}" `
        })
        return allAttributesStr
    }
    // 判断是否为异步执行脚本
    #getScriptExecutionMode(scriptElement) {
        if (!(scriptElement instanceof HTMLElement) || scriptElement.tagName !== 'SCRIPT') {
            return { async: false, defer: false, mode: 'invalid' };
        }
        const hasSrc = scriptElement.hasAttribute('src');
        const hasAsync = scriptElement.hasAttribute('async');
        const hasDefer = scriptElement.hasAttribute('defer');
        const isDynamic = !scriptElement.parentNode;
        // 内联脚本
        if (!hasSrc) {
            return {
                async: false,
                defer: false,
                mode: 'inline'
            };
        }

        if (isDynamic) {
            return {
                async: hasAsync !== false, // 动态脚本默认异步，除非明确设置 async=false
                defer: hasDefer,
                mode: hasAsync !== false ? 'async' : (hasDefer ? 'defer' : 'sync')
            };
        }

        return {
            async: hasAsync,
            defer: hasDefer,
            mode: hasAsync ? 'async' : (hasDefer ? 'defer' : 'sync'),
            _isAsync: hasAsync || hasDefer
        };
    }
    // 拷贝属性 赋值脚本中所有属性
    #handleCopyAttrs(oldEle, newEle) {
        if (oldEle instanceof HTMLElement && newEle instanceof HTMLElement) {
            Array.from(oldEle.attributes).forEach(attr => {
                newEle.setAttribute(attr.name, attr.value)
            })
        }
    }
    // 全局替换机制 获取到全局标签 并且循环替换
    #handleGlobalReplace(target, replaceDomain) {
        const { _origin } = target
        if (target._isScript) {
            const scripts = document.querySelectorAll('script')
            const needReplace = []
            for (let i = 0; i < scripts.length; i++) {
                const src = scripts[i].src
                const { _pathname, _origin, _spareDomain } = scripts[i]
                if (!src || !_pathname || !_origin || !_spareDomain) continue
                const isRequiredReplace = this.#allReplaceList.has(_origin)
                if (isRequiredReplace) {
                    const replaceUrl = this.#handleGetReplaceUrl(_origin, _pathname, replaceDomain) || ''
                    if (replaceUrl) {
                        scripts[i].setAttribute('src', replaceUrl)
                        needReplace.push(scripts[i])
                    }
                }
            }

            let str = ''
            needReplace.forEach(f => {
                const { _origin, _pathname } = f
                this.#resourceMap[_origin][_pathname] = (this.#resourceMap[_origin][_pathname] || 0) + 1
                if (f._isAsync || this.isLoaded) {
                    this.#handleInsertBefore(target, 'script')
                } else {
                    const allAttributesStr = this.#handleGetTagAttr(f)
                    const script = `\<script ${allAttributesStr}><\/script>`
                    str += `${script}\n`
                }
                f.remove()
            })
            if (str) {
                document.write(str)
            }
        }
        if (target._isStyle) {
            const styles = document.querySelectorAll('link')
            const needReplace = []
            for (let i = 0; i < styles.length; i++) {
                const href = styles[i].href
                const { _pathname, _origin, _spareDomain } = styles[i]
                if (!href || !_pathname || !_origin || !_spareDomain) continue
                const isRequiredReplace = this.#allReplaceList.has(_origin)
                if (isRequiredReplace) {
                    const replaceUrl = this.#handleGetReplaceUrl(_origin, _pathname, replaceDomain) || ''
                    if (replaceUrl) {
                        styles[i].setAttribute('href', replaceUrl)
                        needReplace.push(styles[i])
                    }
                }
            }
            needReplace.forEach(f => {
                const { _origin, _pathname } = f
                this.#resourceMap[_origin][_pathname] = (this.#resourceMap[_origin][_pathname] || 0) + 1
                this.#handleInsertBefore(f, 'link')
                f.remove()
            })
        }
    }
    // 获取替换后的链接
    #handleGetReplaceUrl(_origin, _pathname, replaceDomain) {
        const uniqueTag = _origin + _pathname
        let replaceUrl = ''
        let replaceOrigin = ''
        const tempUrl = uniqueTag.replace(_origin, replaceDomain)
        if (!this.#resourceMap[replaceDomain] || !this.#resourceMap[replaceDomain][_pathname]) {
            replaceUrl = tempUrl
            replaceOrigin = replaceDomain
        }
        if (!replaceUrl) return ''
        if (!this.#resourceMap[replaceOrigin]) {
            this.#resourceMap[replaceOrigin] = Object.create(null)
        }
        this.#resourceMap[replaceOrigin][_pathname] = (this.#resourceMap[replaceOrigin][_pathname] || 0) + 1
        return replaceUrl
    }
    // 在当前元素的前插入新的元素 
    #handleInsertBefore(target, tagName) {
        if (target instanceof HTMLElement && tagName) {
            const parentElement = target.parentElement
            if (!parentElement) return
            const tag = document.createElement(tagName)
            this.#handleCopyAttrs(target, tag)
            parentElement.insertBefore(tag, target)
        }
    }
}
const resourceHandler = new ResourceHandler({
    replaceMap: {
        'http://127.0.0.1:3009': 'http://127.0.0.1:5500/web'
    },
    globalReplaceTimes: 1,
    retryTimes: 1
})
console.time('重放时间')